package cn.rtangyy.seckilling.controller;

import cn.rtangyy.seckilling.entity.Order;
import cn.rtangyy.seckilling.entity.SeckillMessage;
import cn.rtangyy.seckilling.entity.SeckillOrder;
import cn.rtangyy.seckilling.entity.User;
import cn.rtangyy.seckilling.exception.GlobalException;
import cn.rtangyy.seckilling.rabbitmq.MQSender;
import cn.rtangyy.seckilling.service.GoodsService;
import cn.rtangyy.seckilling.service.OrderService;
import cn.rtangyy.seckilling.service.SeckillOrderService;
import cn.rtangyy.seckilling.utils.JsonUtil;
import cn.rtangyy.seckilling.vo.GoodsVo;
import cn.rtangyy.seckilling.vo.RespBean;
import cn.rtangyy.seckilling.vo.RespBeanEnum;
import com.wf.captcha.ArithmeticCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/seckill")
@Slf4j
public class SeckillController implements InitializingBean {

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private SeckillOrderService secKillOrderService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MQSender mqSender;

    @Autowired
    private RedisScript<Long> redisScript;

    private Map<Long, Boolean> emptyStockMap = new HashMap<>();

    /**
     * 获取秒杀地址
     *
     * @param user
     * @param goodsId
     * @return
     */
    @GetMapping("/path")
    @ResponseBody
    public RespBean getPath(User user, Long goodsId, String captcha, HttpServletRequest request) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        ValueOperations valueOperations = redisTemplate.opsForValue();

        // 检查验证码
        boolean result = orderService.checkCaptcha(user, goodsId, captcha);
        if (!result) {
            return RespBean.error(RespBeanEnum.ERROR_CAPTCHA);
        }
        String str = orderService.createPath(user, goodsId);
        return RespBean.success(str);

    }

    /**
     * 优化前QPS：447
     * 秒杀静态化之前的 Controller
     *
     * @param model
     * @param user
     * @param goodsId
     * @return
     */
    @PostMapping("/doSeckill2")
    public String doSecKill2(Model model, User user, Long goodsId) {

        if (user == null) {
            return "login";
        }
        model.addAttribute("user", user);
        GoodsVo goodsVo = goodsService.findGoodsVoById(goodsId);


        // 判断库存
        if (goodsVo.getStockCount() < 1) {
            model.addAttribute("errMsg", RespBeanEnum.EMPTY_STOCK.getMessage());
            return "secKillFail";
        }
        //判断是否重复抢购
        boolean exist = secKillOrderService.orderIsExist(user.getId(), goodsId);
        if (exist) {
            model.addAttribute("errMsg", RespBeanEnum.REPEAT_ERROR.getMessage());
            return "secKillFail";
        }


        Order order = orderService.seckill(user, goodsVo);
        model.addAttribute("order", order);
        model.addAttribute("goods", goodsVo);

        return "orderDetail";


    }

    /**
     * 秒杀静态化之后的 Controller 方法
     */
    @PostMapping("/{path}/doSeckill")
    @ResponseBody
    public RespBean doSecKill(User user, Long goodsId, @PathVariable String path) {

        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }

        ValueOperations opsForValue = redisTemplate.opsForValue();

        boolean check = orderService.checkPath(user, goodsId, path);
        if (!check) {
            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }

        // 在 Redis 中存储订单消息，判断是否重复抢购
        SeckillOrder seckillOrder = (SeckillOrder) opsForValue.get("order:" + user.getId() + ":" + goodsId);
        if (null != seckillOrder) {
            return RespBean.error(RespBeanEnum.REPEAT_ERROR);
        }
        // 内存标记操作，减少 Redis 的访问
        Boolean stockIsEmpty = emptyStockMap.get(goodsId);
        if (stockIsEmpty) {
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
        // lua 脚本
        Long stock = (Long) redisTemplate.execute(redisScript, Collections.singletonList("seckillGoods:" + goodsId), Collections.EMPTY_LIST);

        // 预减操作
//        Long stock = opsForValue.decrement("seckillGoods:" + goodsId);
        if (stock < 0) {
            // 防止 Redis 中库存为-1
            emptyStockMap.put(goodsId, true);
            opsForValue.increment("seckillGoods:" + goodsId);
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }

        // rabbitmq 发送异步消息
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        mqSender.sendSeckillMessage(JsonUtil.object2JsonStr(seckillMessage));
        return RespBean.success(0);

    }

    /**
     * 异步获取秒杀结果
     *
     * @param user
     * @param goodsId
     * @return orderId:成功 ，-1 秒杀失败 0 排队中
     */
    @RequestMapping("/result")
    @ResponseBody
    public RespBean getResult(User user, Long goodsId) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        Long result = secKillOrderService.getResult(user, goodsId);
        return RespBean.success(result);
    }

    @RequestMapping(value = "/captcha", method = RequestMethod.GET)
    @ResponseBody
    public void verifyCode(User user, Long goodsId, HttpServletResponse response) {
        if (user == null || goodsId < 0) {
            throw new GlobalException(RespBeanEnum.REQUEST_ILLEGAL);
        }
        // 设置请求头
        response.setContentType("image/jpg");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "No-cache");
        response.setDateHeader("Expires", 0);

        //生成验证码
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 32, 3);
        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId, captcha.text(), 300,
                TimeUnit.SECONDS);
        try {
            captcha.out(response.getOutputStream());
        } catch (IOException e) {
            log.error("验证码生成失败", e.getMessage());
        }
    }

    /**
     * 系统加载初始化，把商品库存数量加载到 Redis
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> list = goodsService.findGoodsVo();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach(goodsVo -> {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());
            emptyStockMap.put(goodsVo.getId(), false);
        });

    }
}
